/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ngl_transport_unix_domain_socket.c
 *
 * Purpose: unix domain socket transport plugin implementation
 *
 * Developer:
 *   wen.gu , 2023-04-13
 *
 * TODO:
 *
 ***************************************************************************/
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h> /* for socket(), connect(), (), and recv() */
#include <sys/select.h>
#include <errno.h>
#include <sys/un.h>

#include "ngl/transport/ngl_transport_unix_domain_socket.h"


typedef struct _ngl_transport_unix_domain_socket_s {
    ngl_transport_t super;
}ngl_transport_unix_domain_socket_t;


/***************************************************************************
 * inner function  implementation
 ***************************************************************************/
static int32_t unix_domain_socket_read(ngl_transport_t* self, uint8_t* buf, uint32_t size) {
    int32_t rc;

    do{
      rc = read(self->fd, buf, size);
    } while (rc == -1 && errno == EINTR);
    return rc;
}

static int32_t unix_domain_socket_write(ngl_transport_t* self, const uint8_t* buf, uint32_t size) {
    int32_t rc;

    do{
      rc = write(self->fd, buf, size);
    } while (rc == -1 && errno == EINTR);
    return rc;
}


static ngl_transport_t* unix_domain_socket_process_client_connection(ngl_transport_t* self, uint32_t timeout_ms) {
    socklen_t cli_size;
    struct sockaddr_un cli;
    /* event from TCP server socket, new connection */
    cli_size = sizeof(cli);
    int32_t in_sock = NGL_TRANSPORT_INVALID_FD;

    fd_set read_set;
    int32_t socket_id = self->fd;

    struct timeval select_timeout = {0};  //timeout time   
    select_timeout.tv_sec = timeout_ms / 1000;   /** */
    select_timeout.tv_usec = (timeout_ms % 1000) * 1000;
    FD_ZERO(&read_set);    
    FD_SET(socket_id, &read_set);    

    int32_t ret = select(socket_id + 1, &read_set, NULL, NULL, &select_timeout);

    if ((ret > 0) && FD_ISSET(socket_id, &read_set)) { 
        if ((in_sock = accept(self->fd, (struct sockaddr *)&cli, &cli_size)) < 0) {
            if (errno == ECONNABORTED) {
                return NULL;
            }

            printf("accept() for socket %d failed: %s\n", self->fd, strerror(errno));
            return NULL;
        }

        ngl_transport_t* trans = ngl_transport_unix_domain_socket_new(in_sock);
        if (!trans) {
            close(in_sock);
        }

        return trans;        
    }

    return NULL;
}

static void unix_domain_socket_free_default(ngl_transport_t* self) {
    if (self->fd != NGL_TRANSPORT_INVALID_FD) {
        shutdown(self->fd, SHUT_RDWR);
    }
    ngl_transport_free_default(self);
}

/***************************************************************************
 * API function  implementation
 ***************************************************************************/



ngl_transport_t* ngl_transport_unix_domain_socket_new(int32_t fd) {
    ngl_transport_unix_domain_socket_t* self = (ngl_transport_unix_domain_socket_t*)malloc(sizeof(ngl_transport_unix_domain_socket_t));
    if (!self) {
        return  NULL;
    }
    memset(self, 0, sizeof(ngl_transport_unix_domain_socket_t));
    ngl_transport_t* super = &self->super;
    ngl_transport_init_instance(super, fd);
    super->read = unix_domain_socket_read;
    super->write = unix_domain_socket_write;
    super->free_fn = unix_domain_socket_free_default;
    super->process_client_connection = unix_domain_socket_process_client_connection;
    super->max_package_size = ngl_transport_max_package_size_default;
    

    return (ngl_transport_t*)self;
}